Data Exploration

The (interactive) correlation heatmap reveals very high correlation among TG compounds.

load("./data.Rdata")
X <- df[df$Diagnosis == "Probable AD", 1:230]
C <- round(cor(X), 2)

heatmaply_cor(C, color = viridis, plot_method = "plotly", dendrogram = F, reorderfun = sort.default(d,w), main = "Correlation Heatmap", file = "heatmap.html", colorbar_thickness = 15, colorbar_len = 0.5)

RQi: Differential expression of metabolites per ApoE genotype

Global test

Performing a Global Test on the serum metabolites of AD patients, correcting for sex (Ho: E4 status has no effect on metabolite levels, Ha: it has an effect), yields a significant difference (p = 0.046) between E4 carriers and non-carriers. Testing if the counts of E4 alleles have an effect on metabolite levels showed no significant nuance.

load("data.Rdata")
gdf <- subset(df, Diagnosis == "Probable AD")
gdf$sex <- as.numeric(gdf$sex) -1
# Binary outcome
gt.b <- globaltest::gt(E4 ~ 1, E4 ~ . - APOE - Diagnosis, data = gdf)
gt.b

# Multinomial outcome
gdf$APOE <- as.factor(gdf$APOE)
gt.m <- globaltest::gt(APOE ~ 1, APOE ~ . - E4 -Diagnosis, data = gdf)
gt.m

Wilcoxon rank-sum test

Triglycerides and diglycerides seem to survive FDR control.

load("ADdata.Rdata")
# Store all observations of Class 1 in C1
C1 <- ADmets[geno$APOEb == "E4NO", 1:230]
# Store all observations of Class 2 in C2
C2 <- ADmets[geno$APOEb == "E4YES", 1:230]

# Create a function to perform the Wilcoxon rank-sum (Mann-Whitney U) test on two vectors
MannWhitneyU <- function(x, y) {
  wilcoxon <- wilcox.test(x, y, paired = FALSE, alternative = "less")
  return(wilcoxon$p.value)
}

# Use purrr::map2 to apply the function to corresponding columns
wilcoxons <- purrr::map2(C1[, 1:230], C2[, 1:230], ~ MannWhitneyU(.x, .y))

# Coerce p_values to dataframe and transpose it
p_values <- t(data.frame(wilcoxons))
# Calculate the FDR-adjusted p-values
p_adj <- p.adjust(wilcoxons, method = "fdr")

results <- data.frame(p_values, p_adj)
# Filter out the non-significant (a=0.05) FDR-adjusted p-values
dplyr::filter(results, p_adj < 0.05)

Carriers of one copy of E4 vs two copies tend to have less histamine, fumaric acid, uracil, triglyceride TG.48.0, phosphatidylcholine PC.36.4, with p < 0.05, however these don’t survive FDRcut at 0.95 (p_adj = 0.97)’

geno$g <- geno$APOE
geno$g[geno$g == "E3E4"] <- geno$g[geno$g == "E2E4"] <- "E4x1"
geno$g[geno$g == "E4E4"] <- "E4x2"
# Store all observations of Class 1 in C1
C1 <- ADmets[geno$g == "E4x1", 1:230]
# Store all observations of Class 2 in C2
C2 <- ADmets[geno$g == "E4x2", 1:230]

# Use purrr::map2 to apply the function to corresponding columns
wilcoxons <- purrr::map2(C1[, 1:230], C2[, 1:230], ~ MannWhitneyU(.x, .y))

# Coerce p_values to dataframe and transpose it
p_values <- t(data.frame(wilcoxons))
# Calculate the FDR-adjusted p-values
p_adj <- p.adjust(wilcoxons, method = "fdr")

results <- data.frame(p_values, p_adj)
# Filter out the non-significant (a=0.05) FDR-adjusted p-values
dplyr::filter(results, p_adj < 0.05)

Testing no E4 vs one E4, no metabolites differ significantly.

geno$g <- geno$APOE
geno$g[geno$g == "E3E4"] <- geno$g[geno$g == "E2E4"] <- "E4x1"
geno$g[geno$APOEb == "E4NO"] <- "E4x0"
# Store all observations of Class 1 in C1
C1 <- ADmets[geno$g == "E4x1", 1:230]
# Store all observations of Class 2 in C2
C2 <- ADmets[geno$g == "E4x0", 1:230]

# Use purrr::map2 to apply the function to corresponding columns
wilcoxons <- purrr::map2(C1[, 1:230], C2[, 1:230], ~ MannWhitneyU(.x, .y))

# Coerce p_values to dataframe and transpose it
p_values <- t(data.frame(wilcoxons))
# Calculate the FDR-adjusted p-values
p_adj <- p.adjust(wilcoxons, method = "fdr")

results <- data.frame(p_values, p_adj)
# Filter out the non-significant (a=0.05) FDR-adjusted p-values
dplyr::filter(results, p_adj < 0.05)

RQii - Classification of metabolites on ApoE class

Function fit

fit <- function(title,
                X,
                y,
                model,
                ctrl = NULL,
                grid = NULL,
                seed = 123, ...) {
  set.seed(seed)
  # Merge X and y into df
  df <- merge.data.frame(X, y)

  # Train the model
  mdl <- caret::train(df[, 1:ncol(X)], df$y,
    method = model,
    tuneGrid = grid,
    trControl = ctrl,
    metric = "ROC",
    preProcess = c("center", "scale"),
    ...
  )
  # Create a confusion matrix and get performance metrics from caret
  obs <- mdl$pred$obs
  preds <- mdl$pred$pred
  cm <- confusionMatrix(obs, preds,
    dnn = c("X0", "X1"), # nolint
    positive = "X1")
  # Predictions
  ys <- as.numeric(obs) -1
  yhats <- mdl$pred$X1
  roc <- roc(ys, yhats,
    levels = c(0, 1),
    ci = TRUE, boot.n = 1000, ci.alpha = 0.95)
  metrics <- data.frame(c(cm$byClass, roc$auc),
    row.names = c(names(cm$byClass), "AUC")
  )
  names(metrics) <- title
  out <- list("metrics" = metrics, "roc" = roc, "model" = mdl)
  return(out)
}

Using 230 metabolites in AD

Binary classification of outcome 0 vs at least 1 E4 allele

load("./data.Rdata")
X <- df[df$Diagnosis == "Probable AD", 1:230]
y <- df[df$Diagnosis == "Probable AD", "E4"]
y <- as.factor(y)
levels(y) <- make.names(levels(y))

# Define the model training parameters, repeated 10-fold cross-valuidation
ctrl <- trainControl(
  method = "repeatedcv",
  number = 10,
  repeats = 1,
  classProbs = TRUE,
  summaryFunction = twoClassSummary,
  savePredictions = TRUE,
  sampling = 'smote',
)
Logistic Regression
lr <- fit(
  title = "Logistic Regression",
  X = X,
  y = y,
  model = "glmnet",
  ctrl = ctrl,
  grid = expand.grid(alpha = c(0), lambda = c(100)))
Error: package MLmetrics is required
Decision Tree
tree <- fit(
  title = "Decision Tree",
  X = X,
  y = y,
  model = "rpart2",
  ctrl = ctrl,
  grid = expand.grid(maxdepth = c(2))
)
XGBoost Forest

Multinomial Classification of number of ApoE E4 alleles based on metabolites

Multinomial Logistic Regression
Decision Tree
mtree$cm
Confusion Matrix and Statistics

          Reference
Prediction X0 X1 X2
        X0 37 35 48
        X1 56 58 69
        X2 18 27 33

Overall Statistics
                                          
               Accuracy : 0.336           
                 95% CI : (0.2887, 0.3858)
    No Information Rate : 0.3937          
    P-Value [Acc > NIR] : 0.9913          
                                          
                  Kappa : 0.0182          
                                          
 Mcnemar's Test P-Value : 4.932e-08       

Statistics by Class:

                     Class: X0 Class: X1 Class: X2
Sensitivity            0.33333    0.4833   0.22000
Specificity            0.69259    0.5211   0.80519
Pos Pred Value         0.30833    0.3169   0.42308
Neg Pred Value         0.71648    0.6869   0.61386
Prevalence             0.29134    0.3150   0.39370
Detection Rate         0.09711    0.1522   0.08661
Detection Prevalence   0.31496    0.4803   0.20472
Balanced Accuracy      0.51296    0.5022   0.51260
XGBoost Forest
mrf$cm
Confusion Matrix and Statistics

          Reference
Prediction X0 X1 X2
        X0 17 14  9
        X1 12 36 13
        X2  9  9  8

Overall Statistics
                                          
               Accuracy : 0.4803          
                 95% CI : (0.3909, 0.5707)
    No Information Rate : 0.4646          
    P-Value [Acc > NIR] : 0.3941          
                                          
                  Kappa : 0.1806          
                                          
 Mcnemar's Test P-Value : 0.8300          

Statistics by Class:

                     Class: X0 Class: X1 Class: X2
Sensitivity             0.4474    0.6102   0.26667
Specificity             0.7416    0.6324   0.81443
Pos Pred Value          0.4250    0.5902   0.30769
Neg Pred Value          0.7586    0.6515   0.78218
Prevalence              0.2992    0.4646   0.23622
Detection Rate          0.1339    0.2835   0.06299
Detection Prevalence    0.3150    0.4803   0.20472
Balanced Accuracy       0.5945    0.6213   0.54055
#Get a performance metrics table
multimetrics <- cbind(mlr$cm$byClass[3,], mtree$cm$byClass[3,], mrf$cm$byClass[3,])
multimetrics
                           [,1]       [,2]       [,3]
Sensitivity          0.26804124 0.22000000 0.26666667
Specificity          0.81690141 0.80519481 0.81443299
Pos Pred Value       0.33333333 0.42307692 0.30769231
Neg Pred Value       0.76567657 0.61386139 0.78217822
Precision            0.33333333 0.42307692 0.30769231
Recall               0.26804124 0.22000000 0.26666667
F1                   0.29714286 0.28947368 0.28571429
Prevalence           0.25459318 0.39370079 0.23622047
Detection Rate       0.06824147 0.08661417 0.06299213
Detection Prevalence 0.20472441 0.20472441 0.20472441
Balanced Accuracy    0.54247132 0.51259740 0.54054983
#Plot ROC curves
mrocs <- list(mlr$roc, mtree$roc, mrf$roc)
# Generate labels
labels <- paste0(names(metrics), ", AUC = ", paste(round(metrics[12, ], 2)))
# httpgd::hgd()
ggroc(mrocs) +
  theme_clean() +
  scale_color_tableau(labels = labels)
Error in ggroc.list(mrocs) : 
  All elements in 'data' must be 'roc' objects.

Projection to Latent Factors

load("thomson.Rdata")
X <- thomson
y <- df[df$Diagnosis == "Probable AD", "E4"]
y <- as.factor(y)
levels(y) <- make.names(levels(y))

Binary classification of outcome 0 vs at least 1 E4 allele

Logistic Regression
ctrl$summaryFunction <- twoClassSummary
lrf <- fit(title = "Logistic Regression",
  X = X,
  y = y,
  model = "glm",
  ctrl = ctrl,
)
Setting direction: controls > cases
Decision Tree
treef <- fit(
  title = "Decision Tree",
  X = X,
  y = y,
  model = "rpart2",
  ctrl = ctrl,
  grid = expand.grid(maxdepth = c(2))
)
Setting direction: controls < cases
XGBoost Forest
param <- data.frame(nrounds = c(10), max_depth = c(2), eta = c(0.3), gamma = c(0), colsample_bytree = 1, min_child_weight = c(1), subsample = c(1))
rff <- fit(title = "XGBoost",
  X = X,
  y = y,
  model = "xgbTree",
  ctrl = ctrl,
  grid = param
)
Setting direction: controls > cases
Model Comparison
#Get a performance metrics table
metricsf <- cbind(lrf$metrics, treef$metrics, rff$metrics)
metricsf
#Plot ROC curves
rocsf <- list(lrf$roc, treef$roc, rff$roc)
# Generate labels
labels <- paste0(names(metrics), ", AUC = ", paste(round(metrics[12, ], 2)))
# httpgd::hgd()
ggroc(rocsf) +
  theme_clean() +
  scale_color_tableau(labels = labels)

Multinomial Classification of number of ApoE E4 alleles based on latent factors

Multinomial Logistic Regression
ctrl$summaryFunction <- multiClassSummary
X <- df[df$Diagnosis == "Probable AD", 1:230]
y <- df[df$Diagnosis == "Probable AD", "APOE"]
y <- as.factor(y)
levels(y) <- make.names(levels(y))
mlrf <- multifit(
  X = X,
  y = y,
  model = "multinom",
  ctrl = ctrl,
  trace = FALSE,
  tuneLength = 3
)
Setting direction: controls < cases
Setting direction: controls < cases
Setting direction: controls < cases
mlrf$cm
Confusion Matrix and Statistics

          Reference
Prediction X0 X1 X2
        X0 59 43 18
        X1 50 80 53
        X2 12 46 20

Overall Statistics
                                          
               Accuracy : 0.4173          
                 95% CI : (0.3673, 0.4686)
    No Information Rate : 0.4436          
    P-Value [Acc > NIR] : 0.8606          
                                          
                  Kappa : 0.0867          
                                          
 Mcnemar's Test P-Value : 0.5277          

Statistics by Class:

                     Class: X0 Class: X1 Class: X2
Sensitivity             0.4876    0.4734   0.21978
Specificity             0.7654    0.5142   0.80000
Pos Pred Value          0.4917    0.4372   0.25641
Neg Pred Value          0.7625    0.5505   0.76568
Prevalence              0.3176    0.4436   0.23885
Detection Rate          0.1549    0.2100   0.05249
Detection Prevalence    0.3150    0.4803   0.20472
Balanced Accuracy       0.6265    0.4938   0.50989
Decision Tree
mtreef <- multifit(
  X = X,
  y = y,
  model = "rpart2",
  ctrl = ctrl,
  tuneLength = 3
)
Setting direction: controls < cases
Setting direction: controls < cases
Setting direction: controls < cases
mtreef$cm
Confusion Matrix and Statistics

          Reference
Prediction X0 X1 X2
        X0 42 29 49
        X1 57 56 70
        X2 19 29 30

Overall Statistics
                                          
               Accuracy : 0.336           
                 95% CI : (0.2887, 0.3858)
    No Information Rate : 0.3911          
    P-Value [Acc > NIR] : 0.9886          
                                          
                  Kappa : 0.0216          
                                          
 Mcnemar's Test P-Value : 1.477e-08       

Statistics by Class:

                     Class: X0 Class: X1 Class: X2
Sensitivity             0.3559    0.4912   0.20134
Specificity             0.7034    0.5243   0.79310
Pos Pred Value          0.3500    0.3060   0.38462
Neg Pred Value          0.7088    0.7071   0.60726
Prevalence              0.3097    0.2992   0.39108
Detection Rate          0.1102    0.1470   0.07874
Detection Prevalence    0.3150    0.4803   0.20472
Balanced Accuracy       0.5297    0.5078   0.49722
XGBoost Forest
mrff <- multifit(
  X = X,
  y = y,
  model = "xgbTree",
  ctrl = ctrl,
  grid = param
)
Setting direction: controls < cases
Setting direction: controls < cases
Setting direction: controls < cases
mrff$cm
Confusion Matrix and Statistics

          Reference
Prediction X0 X1 X2
        X0 17 14  9
        X1 12 36 13
        X2  9  9  8

Overall Statistics
                                          
               Accuracy : 0.4803          
                 95% CI : (0.3909, 0.5707)
    No Information Rate : 0.4646          
    P-Value [Acc > NIR] : 0.3941          
                                          
                  Kappa : 0.1806          
                                          
 Mcnemar's Test P-Value : 0.8300          

Statistics by Class:

                     Class: X0 Class: X1 Class: X2
Sensitivity             0.4474    0.6102   0.26667
Specificity             0.7416    0.6324   0.81443
Pos Pred Value          0.4250    0.5902   0.30769
Neg Pred Value          0.7586    0.6515   0.78218
Prevalence              0.2992    0.4646   0.23622
Detection Rate          0.1339    0.2835   0.06299
Detection Prevalence    0.3150    0.4803   0.20472
Balanced Accuracy       0.5945    0.6213   0.54055
#Get a performance metrics table
multimetricsf <- cbind(mlrf$cm$byClass[3,], mtreef$cm$byClass[3,], mrff$cm$byClass[3,])
multimetricsf
                           [,1]       [,2]       [,3]
Sensitivity          0.21978022 0.20134228 0.26666667
Specificity          0.80000000 0.79310345 0.81443299
Pos Pred Value       0.25641026 0.38461538 0.30769231
Neg Pred Value       0.76567657 0.60726073 0.78217822
Precision            0.25641026 0.38461538 0.30769231
Recall               0.21978022 0.20134228 0.26666667
F1                   0.23668639 0.26431718 0.28571429
Prevalence           0.23884514 0.39107612 0.23622047
Detection Rate       0.05249344 0.07874016 0.06299213
Detection Prevalence 0.20472441 0.20472441 0.20472441
Balanced Accuracy    0.50989011 0.49722287 0.54054983
#Plot ROC curves
mrocsf <- list(mlrf$roc, mtreef$roc, mrff$roc)
# Generate labels
labels <- paste0(names(metrics), ", AUC = ", paste(round(metrics[12, ], 2)))
# httpgd::hgd()
ggroc(mrocsf) +
  theme_clean() +
  scale_color_tableau(labels = labels)
Error in ggroc.list(mrocsf) : 
  All elements in 'data' must be 'roc' objects.
LS0tCnRpdGxlOiAiQW5hbHlzaXMiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OiAKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDQKICAgIHRoZW1lOiBmbGF0bHkKICBwZGZfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogIGh0bWxfbm90ZWJvb2s6IAogICAgdG9jOiB5ZXMKICAgIHRoZW1lOiBmbGF0bHkKICAgIHRvY19kZXB0aDogNQogICAgaGlnaGxpZ2h0OiBweWdtZW50cwplZGl0b3Jfb3B0aW9uczoKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBjb21tZW50ID0gIiIsIHdhcm5pbmcgPSBGQUxTRQopCmBgYAoKYGBge3IgcGFja2FnZXMsIGluY2x1ZGU9RkFMU0V9CnJlcXVpcmUoY2FyZXQpCnJlcXVpcmUoZ2xtbmV0KQpyZXF1aXJlKGRwbHlyKQpyZXF1aXJlKGUxMDcxKQpyZXF1aXJlKEZNcmFkaW8pCnJlcXVpcmUocFJPQykKcmVxdWlyZShyYWdzMnJpZGdlcykKcmVxdWlyZSh4Z2Jvb3N0KQpyZXF1aXJlKGdndGhlbWVzKQpyZXF1aXJlKERNd1IyKQpyZXF1aXJlKGdsb2JhbHRlc3QpCnJlcXVpcmUoaGVhdG1hcGx5KQpgYGAKCiMjIERhdGEgRXhwbG9yYXRpb24KClRoZSAoaW50ZXJhY3RpdmUpIGNvcnJlbGF0aW9uIGhlYXRtYXAgcmV2ZWFscyB2ZXJ5IGhpZ2ggY29ycmVsYXRpb24gYW1vbmcgVEcgY29tcG91bmRzLgoKYGBge3IgaGVhdG1hcH0KbG9hZCgiLi9kYXRhLlJkYXRhIikKWCA8LSBkZltkZiREaWFnbm9zaXMgPT0gIlByb2JhYmxlIEFEIiwgMToyMzBdCkMgPC0gcm91bmQoY29yKFgpLCAyKQoKaGVhdG1hcGx5X2NvcihDLCBjb2xvciA9IHZpcmlkaXMsIHBsb3RfbWV0aG9kID0gInBsb3RseSIsIGRlbmRyb2dyYW0gPSBGLCByZW9yZGVyZnVuID0gc29ydC5kZWZhdWx0KGQsdyksIG1haW4gPSAiQ29ycmVsYXRpb24gSGVhdG1hcCIsIGZpbGUgPSAiaGVhdG1hcC5odG1sIiwgY29sb3JiYXJfdGhpY2tuZXNzID0gMTUsIGNvbG9yYmFyX2xlbiA9IDAuNSkKYGBgCgojIyBSUWk6IERpZmZlcmVudGlhbCBleHByZXNzaW9uIG9mIG1ldGFib2xpdGVzIHBlciBBcG9FIGdlbm90eXBlCgojIyMgR2xvYmFsIHRlc3QKClBlcmZvcm1pbmcgYSBHbG9iYWwgVGVzdCBvbiB0aGUgc2VydW0gbWV0YWJvbGl0ZXMgb2YgQUQgcGF0aWVudHMsIGNvcnJlY3RpbmcgZm9yIHNleCAoSG86IEU0IHN0YXR1cyBoYXMgbm8gZWZmZWN0IG9uIG1ldGFib2xpdGUgbGV2ZWxzLCBIYTogaXQgaGFzIGFuIGVmZmVjdCksIHlpZWxkcyBhIHNpZ25pZmljYW50IGRpZmZlcmVuY2UgKHAgPSAwLjA0NikgYmV0d2VlbiBFNCBjYXJyaWVycyBhbmQgbm9uLWNhcnJpZXJzLiBUZXN0aW5nIGlmIHRoZSBjb3VudHMgb2YgRTQgYWxsZWxlcyBoYXZlIGFuIGVmZmVjdCBvbiBtZXRhYm9saXRlIGxldmVscyBzaG93ZWQgbm8gc2lnbmlmaWNhbnQgbnVhbmNlLgoKYGBge3IgZ3R9CmxvYWQoImRhdGEuUmRhdGEiKQpnZGYgPC0gc3Vic2V0KGRmLCBEaWFnbm9zaXMgPT0gIlByb2JhYmxlIEFEIikKZ2RmJHNleCA8LSBhcy5udW1lcmljKGdkZiRzZXgpIC0xCiMgQmluYXJ5IG91dGNvbWUKZ3QuYiA8LSBnbG9iYWx0ZXN0OjpndChFNCB+IDEsIEU0IH4gLiAtIEFQT0UgLSBEaWFnbm9zaXMsIGRhdGEgPSBnZGYpCmd0LmIKCiMgTXVsdGlub21pYWwgb3V0Y29tZQpnZGYkQVBPRSA8LSBhcy5mYWN0b3IoZ2RmJEFQT0UpCmd0Lm0gPC0gZ2xvYmFsdGVzdDo6Z3QoQVBPRSB+IDEsIEFQT0UgfiAuIC0gRTQgLURpYWdub3NpcywgZGF0YSA9IGdkZikKZ3QubQpgYGAKCiMjIyBXaWxjb3hvbiByYW5rLXN1bSB0ZXN0CgpUcmlnbHljZXJpZGVzIGFuZCBkaWdseWNlcmlkZXMgc2VlbSB0byBzdXJ2aXZlIEZEUiBjb250cm9sLgoKYGBge3Igd2lsY294b24gRTR5ZXMgdnMgRTRub30KbG9hZCgiQURkYXRhLlJkYXRhIikKIyBTdG9yZSBhbGwgb2JzZXJ2YXRpb25zIG9mIENsYXNzIDEgaW4gQzEKQzEgPC0gQURtZXRzW2dlbm8kQVBPRWIgPT0gIkU0Tk8iLCAxOjIzMF0KIyBTdG9yZSBhbGwgb2JzZXJ2YXRpb25zIG9mIENsYXNzIDIgaW4gQzIKQzIgPC0gQURtZXRzW2dlbm8kQVBPRWIgPT0gIkU0WUVTIiwgMToyMzBdCgojIENyZWF0ZSBhIGZ1bmN0aW9uIHRvIHBlcmZvcm0gdGhlIFdpbGNveG9uIHJhbmstc3VtIChNYW5uLVdoaXRuZXkgVSkgdGVzdCBvbiB0d28gdmVjdG9ycwpNYW5uV2hpdG5leVUgPC0gZnVuY3Rpb24oeCwgeSkgewogIHdpbGNveG9uIDwtIHdpbGNveC50ZXN0KHgsIHksIHBhaXJlZCA9IEZBTFNFLCBhbHRlcm5hdGl2ZSA9ICJsZXNzIikKICByZXR1cm4od2lsY294b24kcC52YWx1ZSkKfQoKIyBVc2UgcHVycnI6Om1hcDIgdG8gYXBwbHkgdGhlIGZ1bmN0aW9uIHRvIGNvcnJlc3BvbmRpbmcgY29sdW1ucwp3aWxjb3hvbnMgPC0gcHVycnI6Om1hcDIoQzFbLCAxOjIzMF0sIEMyWywgMToyMzBdLCB+IE1hbm5XaGl0bmV5VSgueCwgLnkpKQoKIyBDb2VyY2UgcF92YWx1ZXMgdG8gZGF0YWZyYW1lIGFuZCB0cmFuc3Bvc2UgaXQKcF92YWx1ZXMgPC0gdChkYXRhLmZyYW1lKHdpbGNveG9ucykpCiMgQ2FsY3VsYXRlIHRoZSBGRFItYWRqdXN0ZWQgcC12YWx1ZXMKcF9hZGogPC0gcC5hZGp1c3Qod2lsY294b25zLCBtZXRob2QgPSAiZmRyIikKCnJlc3VsdHMgPC0gZGF0YS5mcmFtZShwX3ZhbHVlcywgcF9hZGopCiMgRmlsdGVyIG91dCB0aGUgbm9uLXNpZ25pZmljYW50IChhPTAuMDUpIEZEUi1hZGp1c3RlZCBwLXZhbHVlcwpkcGx5cjo6ZmlsdGVyKHJlc3VsdHMsIHBfYWRqIDwgMC4wNSkKYGBgCgpDYXJyaWVycyBvZiBvbmUgY29weSBvZiBFNCB2cyB0d28gY29waWVzIHRlbmQgdG8gaGF2ZSBsZXNzIGhpc3RhbWluZSwgZnVtYXJpYyBhY2lkLCB1cmFjaWwsIHRyaWdseWNlcmlkZSBURy40OC4wLCBwaG9zcGhhdGlkeWxjaG9saW5lIFBDLjM2LjQsIHdpdGggcCBcPCAwLjA1LCBob3dldmVyIHRoZXNlIGRvbid0IHN1cnZpdmUgRkRSY3V0IGF0IDAuOTUgKHBfYWRqID0gMC45NyknCgpgYGB7ciBFNHgxIHZzIEU0eDJ9Cmdlbm8kZyA8LSBnZW5vJEFQT0UKZ2VubyRnW2dlbm8kZyA9PSAiRTNFNCJdIDwtIGdlbm8kZ1tnZW5vJGcgPT0gIkUyRTQiXSA8LSAiRTR4MSIKZ2VubyRnW2dlbm8kZyA9PSAiRTRFNCJdIDwtICJFNHgyIgojIFN0b3JlIGFsbCBvYnNlcnZhdGlvbnMgb2YgQ2xhc3MgMSBpbiBDMQpDMSA8LSBBRG1ldHNbZ2VubyRnID09ICJFNHgxIiwgMToyMzBdCiMgU3RvcmUgYWxsIG9ic2VydmF0aW9ucyBvZiBDbGFzcyAyIGluIEMyCkMyIDwtIEFEbWV0c1tnZW5vJGcgPT0gIkU0eDIiLCAxOjIzMF0KCiMgVXNlIHB1cnJyOjptYXAyIHRvIGFwcGx5IHRoZSBmdW5jdGlvbiB0byBjb3JyZXNwb25kaW5nIGNvbHVtbnMKd2lsY294b25zIDwtIHB1cnJyOjptYXAyKEMxWywgMToyMzBdLCBDMlssIDE6MjMwXSwgfiBNYW5uV2hpdG5leVUoLngsIC55KSkKCiMgQ29lcmNlIHBfdmFsdWVzIHRvIGRhdGFmcmFtZSBhbmQgdHJhbnNwb3NlIGl0CnBfdmFsdWVzIDwtIHQoZGF0YS5mcmFtZSh3aWxjb3hvbnMpKQojIENhbGN1bGF0ZSB0aGUgRkRSLWFkanVzdGVkIHAtdmFsdWVzCnBfYWRqIDwtIHAuYWRqdXN0KHdpbGNveG9ucywgbWV0aG9kID0gImZkciIpCgpyZXN1bHRzIDwtIGRhdGEuZnJhbWUocF92YWx1ZXMsIHBfYWRqKQojIEZpbHRlciBvdXQgdGhlIG5vbi1zaWduaWZpY2FudCAoYT0wLjA1KSBGRFItYWRqdXN0ZWQgcC12YWx1ZXMKZHBseXI6OmZpbHRlcihyZXN1bHRzLCBwX2FkaiA8IDAuMDUpCmBgYAoKVGVzdGluZyBubyBFNCB2cyBvbmUgRTQsIG5vIG1ldGFib2xpdGVzIGRpZmZlciBzaWduaWZpY2FudGx5LgoKYGBge3IgRTRubyB2cyBFNHgxfQpnZW5vJGcgPC0gZ2VubyRBUE9FCmdlbm8kZ1tnZW5vJGcgPT0gIkUzRTQiXSA8LSBnZW5vJGdbZ2VubyRnID09ICJFMkU0Il0gPC0gIkU0eDEiCmdlbm8kZ1tnZW5vJEFQT0ViID09ICJFNE5PIl0gPC0gIkU0eDAiCiMgU3RvcmUgYWxsIG9ic2VydmF0aW9ucyBvZiBDbGFzcyAxIGluIEMxCkMxIDwtIEFEbWV0c1tnZW5vJGcgPT0gIkU0eDEiLCAxOjIzMF0KIyBTdG9yZSBhbGwgb2JzZXJ2YXRpb25zIG9mIENsYXNzIDIgaW4gQzIKQzIgPC0gQURtZXRzW2dlbm8kZyA9PSAiRTR4MCIsIDE6MjMwXQoKIyBVc2UgcHVycnI6Om1hcDIgdG8gYXBwbHkgdGhlIGZ1bmN0aW9uIHRvIGNvcnJlc3BvbmRpbmcgY29sdW1ucwp3aWxjb3hvbnMgPC0gcHVycnI6Om1hcDIoQzFbLCAxOjIzMF0sIEMyWywgMToyMzBdLCB+IE1hbm5XaGl0bmV5VSgueCwgLnkpKQoKIyBDb2VyY2UgcF92YWx1ZXMgdG8gZGF0YWZyYW1lIGFuZCB0cmFuc3Bvc2UgaXQKcF92YWx1ZXMgPC0gdChkYXRhLmZyYW1lKHdpbGNveG9ucykpCiMgQ2FsY3VsYXRlIHRoZSBGRFItYWRqdXN0ZWQgcC12YWx1ZXMKcF9hZGogPC0gcC5hZGp1c3Qod2lsY294b25zLCBtZXRob2QgPSAiZmRyIikKCnJlc3VsdHMgPC0gZGF0YS5mcmFtZShwX3ZhbHVlcywgcF9hZGopCiMgRmlsdGVyIG91dCB0aGUgbm9uLXNpZ25pZmljYW50IChhPTAuMDUpIEZEUi1hZGp1c3RlZCBwLXZhbHVlcwpkcGx5cjo6ZmlsdGVyKHJlc3VsdHMsIHBfYWRqIDwgMC4wNSkKYGBgCgojIyBSUWlpIC0gQ2xhc3NpZmljYXRpb24gb2YgbWV0YWJvbGl0ZXMgb24gQXBvRSBjbGFzcwoKIyMjIEZ1bmN0aW9uIGZpdAoKYGBge3IgZml0X2RlZn0KZml0IDwtIGZ1bmN0aW9uKHRpdGxlLAogICAgICAgICAgICAgICAgWCwKICAgICAgICAgICAgICAgIHksCiAgICAgICAgICAgICAgICBtb2RlbCwKICAgICAgICAgICAgICAgIGN0cmwgPSBOVUxMLAogICAgICAgICAgICAgICAgZ3JpZCA9IE5VTEwsCiAgICAgICAgICAgICAgICBzZWVkID0gMTIzLCAuLi4pIHsKICBzZXQuc2VlZChzZWVkKQogICMgTWVyZ2UgWCBhbmQgeSBpbnRvIGRmCiAgZGYgPC0gbWVyZ2UuZGF0YS5mcmFtZShYLCB5KQoKICAjIFRyYWluIHRoZSBtb2RlbAogIG1kbCA8LSBjYXJldDo6dHJhaW4oZGZbLCAxOm5jb2woWCldLCBkZiR5LAogICAgbWV0aG9kID0gbW9kZWwsCiAgICB0dW5lR3JpZCA9IGdyaWQsCiAgICB0ckNvbnRyb2wgPSBjdHJsLAogICAgbWV0cmljID0gIlJPQyIsCiAgICBwcmVQcm9jZXNzID0gYygiY2VudGVyIiwgInNjYWxlIiksCiAgICAuLi4KICApCiAgIyBDcmVhdGUgYSBjb25mdXNpb24gbWF0cml4IGFuZCBnZXQgcGVyZm9ybWFuY2UgbWV0cmljcyBmcm9tIGNhcmV0CiAgb2JzIDwtIG1kbCRwcmVkJG9icwogIHByZWRzIDwtIG1kbCRwcmVkJHByZWQKICBjbSA8LSBjb25mdXNpb25NYXRyaXgob2JzLCBwcmVkcywKICAgIGRubiA9IGMoIlgwIiwgIlgxIiksICMgbm9saW50CiAgICBwb3NpdGl2ZSA9ICJYMSIpCiAgIyBQcmVkaWN0aW9ucwogIHlzIDwtIGFzLm51bWVyaWMob2JzKSAtMQogIHloYXRzIDwtIG1kbCRwcmVkJFgxCiAgcm9jIDwtIHJvYyh5cywgeWhhdHMsCiAgICBsZXZlbHMgPSBjKDAsIDEpLAogICAgY2kgPSBUUlVFLCBib290Lm4gPSAxMDAwLCBjaS5hbHBoYSA9IDAuOTUpCiAgbWV0cmljcyA8LSBkYXRhLmZyYW1lKGMoY20kYnlDbGFzcywgcm9jJGF1YyksCiAgICByb3cubmFtZXMgPSBjKG5hbWVzKGNtJGJ5Q2xhc3MpLCAiQVVDIikKICApCiAgbmFtZXMobWV0cmljcykgPC0gdGl0bGUKICBvdXQgPC0gbGlzdCgibWV0cmljcyIgPSBtZXRyaWNzLCAicm9jIiA9IHJvYywgIm1vZGVsIiA9IG1kbCkKICByZXR1cm4ob3V0KQp9CmBgYAoKIyMjIFVzaW5nIDIzMCBtZXRhYm9saXRlcyBpbiBBRAoKIyMjIyBCaW5hcnkgY2xhc3NpZmljYXRpb24gb2Ygb3V0Y29tZSAwIHZzIGF0IGxlYXN0IDEgRTQgYWxsZWxlCgpgYGB7ciBiaW5hcnlfcHJlcGFyYXRpb259CmxvYWQoIi4vZGF0YS5SZGF0YSIpClggPC0gZGZbZGYkRGlhZ25vc2lzID09ICJQcm9iYWJsZSBBRCIsIDE6MjMwXQp5IDwtIGRmW2RmJERpYWdub3NpcyA9PSAiUHJvYmFibGUgQUQiLCAiRTQiXQp5IDwtIGFzLmZhY3Rvcih5KQpsZXZlbHMoeSkgPC0gbWFrZS5uYW1lcyhsZXZlbHMoeSkpCgojIERlZmluZSB0aGUgbW9kZWwgdHJhaW5pbmcgcGFyYW1ldGVycywgcmVwZWF0ZWQgMTAtZm9sZCBjcm9zcy12YWx1aWRhdGlvbgpjdHJsIDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAicmVwZWF0ZWRjdiIsCiAgbnVtYmVyID0gMTAsCiAgcmVwZWF0cyA9IDEsCiAgY2xhc3NQcm9icyA9IFRSVUUsCiAgc3VtbWFyeUZ1bmN0aW9uID0gdHdvQ2xhc3NTdW1tYXJ5LAogIHNhdmVQcmVkaWN0aW9ucyA9IFRSVUUsCiAgc2FtcGxpbmcgPSAnc21vdGUnLAopCmBgYAoKIyMjIyMgTG9naXN0aWMgUmVncmVzc2lvbgoKYGBge3J9CmxyIDwtIGZpdCgKICB0aXRsZSA9ICJMb2dpc3RpYyBSZWdyZXNzaW9uIiwKICBYID0gWCwKICB5ID0geSwKICBtb2RlbCA9ICJnbG1uZXQiLAogIGN0cmwgPSBjdHJsLAogIGdyaWQgPSBleHBhbmQuZ3JpZChhbHBoYSA9IGMoMCksIGxhbWJkYSA9IGMoMTAwKSkpCmBgYAoKIyMjIyMgRGVjaXNpb24gVHJlZQoKYGBge3IgfQp0cmVlIDwtIGZpdCgKICB0aXRsZSA9ICJEZWNpc2lvbiBUcmVlIiwKICBYID0gWCwKICB5ID0geSwKICBtb2RlbCA9ICJycGFydDIiLAogIGN0cmwgPSBjdHJsLAogIGdyaWQgPSBleHBhbmQuZ3JpZChtYXhkZXB0aCA9IGMoMikpCikKdHJlZSRtZXRyaWNzCmBgYAoKIyMjIyMgWEdCb29zdCBGb3Jlc3QKCmBgYHtyfQpwYXJhbSA8LSBkYXRhLmZyYW1lKG5yb3VuZHMgPSBjKDEwKSwgbWF4X2RlcHRoID0gYygyKSwgZXRhID0gYygwLjMpLCBnYW1tYSA9IGMoMCksIGNvbHNhbXBsZV9ieXRyZWUgPSBjKDAuNSksIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEpLCBzdWJzYW1wbGUgPSBjKDEpKQpyZiA8LSBmaXQodGl0bGUgPSAiWEdCb29zdCIsCiAgWCA9IFgsCiAgeSA9IHksCiAgbW9kZWwgPSAieGdiVHJlZSIsCiAgY3RybCA9IGN0cmwsCiAgZ3JpZCA9IHBhcmFtCikKcmYkbWV0cmljcwpgYGAKCmBgYHtyfQojR2V0IGEgcGVyZm9ybWFuY2UgbWV0cmljcyB0YWJsZQptZXRyaWNzIDwtIGNiaW5kKGxyJG1ldHJpY3MsIHRyZWUkbWV0cmljcywgcmYkbWV0cmljcykKbWV0cmljcwojUGxvdCBST0MgY3VydmVzCnJvY3MgPC0gbGlzdChsciRyb2MsIHRyZWUkcm9jLCByZiRyb2MpCiMgR2VuZXJhdGUgbGFiZWxzCmxhYmVscyA8LSBwYXN0ZTAobmFtZXMobWV0cmljcyksICIsIEFVQyA9ICIsIHBhc3RlKHJvdW5kKG1ldHJpY3NbMTIsIF0sIDIpKSkKIyBodHRwZ2Q6OmhnZCgpCmdncm9jKHJvY3MpICsKICB0aGVtZV9jbGVhbigpICsKICBzY2FsZV9jb2xvcl90YWJsZWF1KGxhYmVscyA9IGxhYmVscykKYGBgCgojIyMjIE11bHRpbm9taWFsIENsYXNzaWZpY2F0aW9uIG9mIG51bWJlciBvZiBBcG9FIEU0IGFsbGVsZXMgYmFzZWQgb24gbWV0YWJvbGl0ZXMKCmBgYHtyIG11bHRpZml0fQptdWx0aWZpdCA8LSBmdW5jdGlvbigKICAgICAgICAgICAgICAgIFgsCiAgICAgICAgICAgICAgICB5LAogICAgICAgICAgICAgICAgbW9kZWwsCiAgICAgICAgICAgICAgICBjdHJsID0gTlVMTCwKICAgICAgICAgICAgICAgIGdyaWQgPSBOVUxMLAogICAgICAgICAgICAgICAgc2VlZCA9IDEyMywgLi4uKSB7CiAgc2V0LnNlZWQoc2VlZCkKICAjIE1lcmdlIFggYW5kIHkgaW50byBkZgogIGRmIDwtIG1lcmdlLmRhdGEuZnJhbWUoWCwgeSkKCiAgIyBUcmFpbiB0aGUgbW9kZWwKICBtZGwgPC0gY2FyZXQ6OnRyYWluKGRmWywgMTpuY29sKFgpXSwgZGYkeSwKICAgIG1ldGhvZCA9IG1vZGVsLAogICAgdHVuZUdyaWQgPSBncmlkLAogICAgdHJDb250cm9sID0gY3RybCwKICAgIG1ldHJpYyA9ICJhY2N1cmFjeSIsCiAgICAuLi4KICApCiAgIyBDcmVhdGUgYSBjb25mdXNpb24gbWF0cml4IGFuZCBnZXQgcGVyZm9ybWFuY2UgbWV0cmljcyBmcm9tIGNhcmV0CiAgb2JzIDwtIG1kbCRwcmVkJG9icwogIHByZWRzIDwtIG1kbCRwcmVkJHByZWQKICBjbSA8LSBjb25mdXNpb25NYXRyaXgob2JzLCBwcmVkcykKICAjIFByZWRpY3Rpb25zCiAgeXMgPC0gYXMubnVtZXJpYyhvYnMpIC0xCiAgeWhhdHMgPC0gYXMubnVtZXJpYyhwcmVkcykgLTEKICByb2MgPC0gbXVsdGljbGFzcy5yb2MoeXMsIHloYXRzKQogIG91dCA8LSBsaXN0KCJjbSIgPSBjbSwgInJvYyIgPSByb2MsICJtb2RlbCIgPSBtZGwpCiAgcmV0dXJuKG91dCkKfQpgYGAKCmBgYHtyIG11bHRpX3ByZXBhcmF0aW9ufQpjdHJsJHN1bW1hcnlGdW5jdGlvbiA9IG11bHRpQ2xhc3NTdW1tYXJ5ClggPC0gZGZbZGYkRGlhZ25vc2lzID09ICJQcm9iYWJsZSBBRCIsIDE6MjMwXQp5IDwtIGRmW2RmJERpYWdub3NpcyA9PSAiUHJvYmFibGUgQUQiLCAiQVBPRSJdCnkgPC0gYXMuZmFjdG9yKHkpCmxldmVscyh5KSA8LSBtYWtlLm5hbWVzKGxldmVscyh5KSkKYGBgCgojIyMjIyBNdWx0aW5vbWlhbCBMb2dpc3RpYyBSZWdyZXNzaW9uCgpgYGB7ciBtbG9naXR9CmxpYnJhcnkobm5ldCkKbWxyIDwtIG11bHRpZml0KAogIFggPSBYLAogIHkgPSB5LAogIG1vZGVsID0gIm11bHRpbm9tIiwKICBjdHJsID0gY3RybCwKICB0cmFjZSA9IEZBTFNFLAogIHR1bmVMZW5ndGggPSAxCikKbWxyJGNtCmBgYAoKIyMjIyMgRGVjaXNpb24gVHJlZQoKYGBge3IgfQptdHJlZSA8LSBtdWx0aWZpdCgKICBYID0gWCwKICB5ID0geSwKICBtb2RlbCA9ICJycGFydDIiLAogIGN0cmwgPSBjdHJsLAogIHR1bmVMZW5ndGggPSAzCikKbXRyZWUkY20KYGBgCgojIyMjIyBYR0Jvb3N0IEZvcmVzdAoKYGBge3IgbXVsdGlfeGdifQptcmYgPC0gbXVsdGlmaXQoCiAgWCA9IFgsCiAgeSA9IHksCiAgbW9kZWwgPSAieGdiVHJlZSIsCiAgY3RybCA9IGN0cmwsCiAgZ3JpZCA9IHBhcmFtCikKbXJmJGNtCmBgYAoKYGBge3J9CiNHZXQgYSBwZXJmb3JtYW5jZSBtZXRyaWNzIHRhYmxlCm11bHRpbWV0cmljcyA8LSBjYmluZChtbHIkY20kYnlDbGFzc1szLF0sIG10cmVlJGNtJGJ5Q2xhc3NbMyxdLCBtcmYkY20kYnlDbGFzc1szLF0pCm11bHRpbWV0cmljcwojUGxvdCBST0MgY3VydmVzCm1yb2NzIDwtIGxpc3QobWxyJHJvYywgbXRyZWUkcm9jLCBtcmYkcm9jKQojIEdlbmVyYXRlIGxhYmVscwpsYWJlbHMgPC0gcGFzdGUwKG5hbWVzKG1ldHJpY3MpLCAiLCBBVUMgPSAiLCBwYXN0ZShyb3VuZChtZXRyaWNzWzEyLCBdLCAyKSkpCmBgYAoKIyMjIFByb2plY3Rpb24gdG8gTGF0ZW50IEZhY3RvcnMKCmBgYHtyIGdldF90aG9tc29uLCBldmFsID1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KcHJvamVjdCA8LSBmdW5jdGlvbihYLCBtLCBzZWVkKSB7CiAgc2V0LnNlZWQoc2VlZCkKICBYIDwtIHNjYWxlKGFzLm1hdHJpeChYKSkKICBjb3YgPC0gY29yKFgpCgogICMgRmluZCByZWR1bmRhbnQgZmVhdHVyZXMKICBmaWx0ZXIgPC0gUkYoY292KQoKICAjIEZpbHRlciBvdXQgcmVkdW5kYW50IGZlYXR1cmVzCiAgZmlsdGVyZWQgPC0gc3ViU2V0KFgsIGZpbHRlcikKCiAgIyBSZWd1bGFyaXplZCBjb3JyZWxhdGlvbiBtYXRyaXggZXN0aW1hdGlvbgogIE0gPC0gcmVnY29yKGZpbHRlcmVkKQoKICAjIEdldCB0aGUgcmVndWxhcml6ZWQgY29ycmVsYXRpb24gbWF0cml4IG9mIHRoZSBmaWx0ZXJlZCBkYXRhc2V0CiAgUiA8LSBNJG9wdENvcgoKICBtbGZhIDwtIG1sRkEoUiwgbSA9IG0pCiAgdGhvbXNvbiA8LSBmYWNTY29yZShmaWx0ZXJlZCwgbWxmYSRMb2FkaW5ncywgbWxmYSRVbmlxdWVuZXNzKQogIHJldHVybih0aG9tc29uKQp9CmBgYAoKYGBge3J9CmxvYWQoInRob21zb24uUmRhdGEiKQpYIDwtIHRob21zb24KeSA8LSBkZltkZiREaWFnbm9zaXMgPT0gIlByb2JhYmxlIEFEIiwgIkU0Il0KeSA8LSBhcy5mYWN0b3IoeSkKbGV2ZWxzKHkpIDwtIG1ha2UubmFtZXMobGV2ZWxzKHkpKQpgYGAKCiMjIyMgQmluYXJ5IGNsYXNzaWZpY2F0aW9uIG9mIG91dGNvbWUgMCB2cyBhdCBsZWFzdCAxIEU0IGFsbGVsZQoKIyMjIyMgTG9naXN0aWMgUmVncmVzc2lvbgoKYGBge3J9CmN0cmwkc3VtbWFyeUZ1bmN0aW9uIDwtIHR3b0NsYXNzU3VtbWFyeQpscmYgPC0gZml0KHRpdGxlID0gIkxvZ2lzdGljIFJlZ3Jlc3Npb24iLAogIFggPSBYLAogIHkgPSB5LAogIG1vZGVsID0gImdsbSIsCiAgY3RybCA9IGN0cmwsCikKYGBgCgojIyMjIyBEZWNpc2lvbiBUcmVlCgpgYGB7ciB9CnRyZWVmIDwtIGZpdCgKICB0aXRsZSA9ICJEZWNpc2lvbiBUcmVlIiwKICBYID0gWCwKICB5ID0geSwKICBtb2RlbCA9ICJycGFydDIiLAogIGN0cmwgPSBjdHJsLAogIGdyaWQgPSBleHBhbmQuZ3JpZChtYXhkZXB0aCA9IGMoMikpCikKYGBgCgojIyMjIyBYR0Jvb3N0IEZvcmVzdAoKYGBge3J9CnBhcmFtIDwtIGRhdGEuZnJhbWUobnJvdW5kcyA9IGMoMTApLCBtYXhfZGVwdGggPSBjKDIpLCBldGEgPSBjKDAuMyksIGdhbW1hID0gYygwKSwgY29sc2FtcGxlX2J5dHJlZSA9IDEsIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEpLCBzdWJzYW1wbGUgPSBjKDEpKQpyZmYgPC0gZml0KHRpdGxlID0gIlhHQm9vc3QiLAogIFggPSBYLAogIHkgPSB5LAogIG1vZGVsID0gInhnYlRyZWUiLAogIGN0cmwgPSBjdHJsLAogIGdyaWQgPSBwYXJhbQopCmBgYAoKIyMjIyMgTW9kZWwgQ29tcGFyaXNvbgoKYGBge3J9CiNHZXQgYSBwZXJmb3JtYW5jZSBtZXRyaWNzIHRhYmxlCm1ldHJpY3NmIDwtIGNiaW5kKGxyZiRtZXRyaWNzLCB0cmVlZiRtZXRyaWNzLCByZmYkbWV0cmljcykKbWV0cmljc2YKI1Bsb3QgUk9DIGN1cnZlcwpyb2NzZiA8LSBsaXN0KGxyZiRyb2MsIHRyZWVmJHJvYywgcmZmJHJvYykKIyBHZW5lcmF0ZSBsYWJlbHMKbGFiZWxzIDwtIHBhc3RlMChuYW1lcyhtZXRyaWNzKSwgIiwgQVVDID0gIiwgcGFzdGUocm91bmQobWV0cmljc1sxMiwgXSwgMikpKQojIGh0dHBnZDo6aGdkKCkKZ2dyb2Mocm9jc2YpICsKICB0aGVtZV9jbGVhbigpICsKICBzY2FsZV9jb2xvcl90YWJsZWF1KGxhYmVscyA9IGxhYmVscykKYGBgCgojIyMjIE11bHRpbm9taWFsIENsYXNzaWZpY2F0aW9uIG9mIG51bWJlciBvZiBBcG9FIEU0IGFsbGVsZXMgYmFzZWQgb24gbGF0ZW50IGZhY3RvcnMKCiMjIyMjIE11bHRpbm9taWFsIExvZ2lzdGljIFJlZ3Jlc3Npb24KCmBgYHtyIH0KY3RybCRzdW1tYXJ5RnVuY3Rpb24gPC0gbXVsdGlDbGFzc1N1bW1hcnkKWCA8LSBkZltkZiREaWFnbm9zaXMgPT0gIlByb2JhYmxlIEFEIiwgMToyMzBdCnkgPC0gZGZbZGYkRGlhZ25vc2lzID09ICJQcm9iYWJsZSBBRCIsICJBUE9FIl0KeSA8LSBhcy5mYWN0b3IoeSkKbGV2ZWxzKHkpIDwtIG1ha2UubmFtZXMobGV2ZWxzKHkpKQptbHJmIDwtIG11bHRpZml0KAogIFggPSBYLAogIHkgPSB5LAogIG1vZGVsID0gIm11bHRpbm9tIiwKICBjdHJsID0gY3RybCwKICB0cmFjZSA9IEZBTFNFLAogIHR1bmVMZW5ndGggPSAzCikKbWxyZiRjbQpgYGAKCiMjIyMjIERlY2lzaW9uIFRyZWUKCmBgYHtyIG11bHRpX3RyZWV9Cm10cmVlZiA8LSBtdWx0aWZpdCgKICBYID0gWCwKICB5ID0geSwKICBtb2RlbCA9ICJycGFydDIiLAogIGN0cmwgPSBjdHJsLAogIHR1bmVMZW5ndGggPSAzCikKbXRyZWVmJGNtCmBgYAoKIyMjIyMgWEdCb29zdCBGb3Jlc3QKCmBgYHtyIH0KbXJmZiA8LSBtdWx0aWZpdCgKICBYID0gWCwKICB5ID0geSwKICBtb2RlbCA9ICJ4Z2JUcmVlIiwKICBjdHJsID0gY3RybCwKICBncmlkID0gcGFyYW0KKQptcmZmJGNtCmBgYAoKYGBge3J9CiNHZXQgYSBwZXJmb3JtYW5jZSBtZXRyaWNzIHRhYmxlCm11bHRpbWV0cmljc2YgPC0gY2JpbmQobWxyZiRjbSRieUNsYXNzWzMsXSwgbXRyZWVmJGNtJGJ5Q2xhc3NbMyxdLCBtcmZmJGNtJGJ5Q2xhc3NbMyxdKQptdWx0aW1ldHJpY3NmCiNQbG90IFJPQyBjdXJ2ZXMKbXJvY3NmIDwtIGxpc3QobWxyZiRyb2MsIG10cmVlZiRyb2MsIG1yZmYkcm9jKQojIEdlbmVyYXRlIGxhYmVscwpsYWJlbHMgPC0gcGFzdGUwKG5hbWVzKG1ldHJpY3MpLCAiLCBBVUMgPSAiLCBwYXN0ZShyb3VuZChtZXRyaWNzWzEyLCBdLCAyKSkpCiMgaHR0cGdkOjpoZ2QoKQpnZ3JvYyhtcm9jc2YpICsKICB0aGVtZV9jbGVhbigpICsKICBzY2FsZV9jb2xvcl90YWJsZWF1KGxhYmVscyA9IGxhYmVscykKYGBgCg==